package shoozhoo.utils;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;

import android.media.AudioFormat;
import android.util.Log;

/*
 * ループ用のwavファイルの解析
 * 
 * データはすべてメモリ上に読み込みます。
 * このクラスは楽器の数ミリ～100ミリ秒程度の非常に短いwavファイルで使用するべきです。
 */
public class InstrumentWaveFile {
	private static final String TAG = "TAG";
	private long fileSize;	// 先頭のRIFFとファイルサイズを除いたファイルサイズ(実サイズ-8)
	
	private int formatId;
	private int channel;
	private int sampleRate;
	private int bytePerSec;
	private int blockSize;
	private int sampleBit;
	
	private int dataSize;
	private byte[] data8bit;
	private short[] data16bit;
	
	public InstrumentWaveFile(InputStream is) throws IOException{
		DataInputStream dis = null;
		try{
			dis = new DataInputStream(is);
			byte[] b = new byte[4];

			dis.read(b);
			if(!this.match(b, "RIFF")){
				throw new IllegalArgumentException("Bad wave file [RIFF]");
			}
			
			this.fileSize = (int)readlittleUInt(dis);
			Log.v(TAG, "Wave Size "+this.fileSize);

			dis.read(b);
			if(!this.match(b, "WAVE")){
				throw new IllegalArgumentException("Bad wave file [WAVE]");
			}
			while(true){
				byte[] tag = new byte[4];
				dis.read(tag);
				
				if(this.match(tag, "fmt ")){
					parseFmt(dis);
				}else if(this.match(tag, "data")){
					this.dataSize = (int)readlittleUInt(dis);
					if(this.sampleBit==8){
						this.data8bit = new byte[this.dataSize];
						dis.read(this.data8bit);
					}else{
						this.read16BitData(dis);
					}
					break;
				}
			}
			Log.v(TAG, "Format "+formatId+"/Channel "+channel+"/" +
					"SampleRate "+sampleRate+"/BytePerSec "+bytePerSec+"/" +
					"BlockSize "+blockSize+"/SampleBit "+sampleBit+"/" +
					"DataSize "+dataSize);
		}finally{
			try{
				dis.close();
			}catch (Exception e) {}
			try{
				is.close();
			}catch (Exception e) {}
		}
	}
	
	private void read16BitData(DataInputStream is) throws IOException{
		this.data16bit = new short[this.dataSize/2];
		Log.v(TAG, "size "+this.dataSize+"/"+this.data16bit.length);
		for(int i=0; i<this.data16bit.length; i++){
			this.data16bit[i] = Short.reverseBytes(is.readShort());
		}
	}
	
	public byte[] get8bitData(){
		return this.data8bit;
	}
	public short[] get16bitData(){
		return this.data16bit;
	}
	
	private void parseFmt(DataInputStream is) throws IOException{
		// サイズ
		int size = (int)readlittleUInt(is);
		this.formatId = Short.reverseBytes(is.readShort());
		this.channel = Short.reverseBytes(is.readShort());
		this.sampleRate = (int)readlittleUInt(is);
		this.bytePerSec = (int)readlittleUInt(is);
		this.blockSize = Short.reverseBytes(is.readShort());
		this.sampleBit = Short.reverseBytes(is.readShort());
		is.skip(size-16);
	}
	
	private boolean match(byte[] b, String s){
		try{
			for(int i=0; i<b.length; i++){
				if(b[i]!=s.charAt(i)){
					return false;
				}
			}
			return true;
		}catch (Exception e) {
			return false;
		}
	}
	
	private long readlittleUInt(DataInputStream is) throws IOException{
		byte[] b = new byte[4];
		is.read(b);
		long rval = (long)b[0] & 0xFF;
		rval += ((long)b[1] & 0xFF) << 8;
		rval += ((long)b[2] & 0xFF) << 16;
		rval += ((long)b[3] & 0xFF) << 24;
		return rval;
	}
	/*
	private int byte2littleUShort(byte[] b){
		int rval = (int)b[0] & 0xFF;
		rval += ((int)b[1] & 0xFF) << 8;
		return rval;
	}*/

	public int getFormatId() {
		return formatId;
	}

	public int getChannel() {
		return channel;
	}

	public int getSampleRate() {
		return sampleRate;
	}

	public int getBytePerSec() {
		return bytePerSec;
	}

	public int getBlockSize() {
		return blockSize;
	}

	public int getSampleBit() {
		return sampleBit;
	}

	public long getDataSize() {
		return dataSize;
	}
	
	public int getAudioEncoding(){
		switch (this.sampleBit) {
		case 8:
			return AudioFormat.ENCODING_PCM_8BIT;
		case 16:
			return AudioFormat.ENCODING_PCM_16BIT;
		default:
			return AudioFormat.ENCODING_INVALID;
		}
	}
	
	public int getAudioChannel(){
		switch (this.channel) {
		case 1:
			return AudioFormat.CHANNEL_CONFIGURATION_MONO;
		case 2:
			return AudioFormat.CHANNEL_CONFIGURATION_STEREO;
		default:
			return AudioFormat.CHANNEL_CONFIGURATION_INVALID;
		}
	}
	
}
